﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Json;

namespace Niknak.Social.Twitter
{
    public class Utils
    {
        private const string APIDATEFORMAT = "ddd MMM dd HH:mm:ss %zzzz yyyy";

        internal static Tweet ParseTweetJson<Tweet, User, Media, MediaSize>(JsonObject tweet)
            where Tweet : IMessage, new()
            where User : IUser, new()
            where Media : IMedia, new()
            where MediaSize : IMediaSize, new()
        {
            Tweet message = new Tweet();

            try
            {
                DateTime createdAt = DateTime.ParseExact(tweet["created_at"].ReadAs<string>(), APIDATEFORMAT, null);

                message.MessageId = tweet["id_str"].ReadAs<string>();
                message.CreatedAt = createdAt;
                message.InReplyToUserId = tweet["in_reply_to_user_id_str"] != null ? tweet["in_reply_to_user_id_str"].ReadAs<string>() : string.Empty;
                message.InReplyToMessageId = tweet["in_reply_to_status_id_str"] != null ? tweet["in_reply_to_status_id_str"].ReadAs<string>() : string.Empty;
                message.Source = tweet["source"].ReadAs<string>();
                message.Text = tweet["text"].ReadAs<string>();
                message.TextAsHtml = ParseTweetEntitiesToHtml(message.Text, tweet["entities"].ToJsonObject());
                message.Author = new User()
                {
                    UserId = tweet["user"]["id"].ReadAs<string>(),
                    ScreenName = tweet["user"]["screen_name"].ReadAs<string>(),
                    Name = tweet["user"]["name"].ReadAs<string>(),
                    ProfileImageUrl = tweet["user"]["profile_image_url"].ReadAs<string>()
                };

                if (tweet.ContainsKey("retweeted_status"))
                    message.RetweetedMessage = ParseTweetJson<Tweet, User, Media, MediaSize>(tweet["retweeted_status"].ToJsonObject());

                if (tweet.ContainsKey("entities") && tweet["entities"].ContainsKey("media"))
                    message.Media = tweet["entities"]["media"].Select<KeyValuePair<string, JsonValue>, IMedia>(m => new Media()
                    {
                        CreatedAt = createdAt,
                        MediaId = m.Value["id_str"].ReadAs<string>(),
                        DisplayUrl = m.Value["display_url"].ReadAs<string>(),
                        ExpandedUrl = m.Value["expanded_url"].ReadAs<string>(),
                        MediaType = m.Value["type"].ReadAs<string>(),
                        MediaUrl = m.Value["media_url"].ReadAs<string>(),
                        MediaUrlHttps = m.Value["media_url_https"].ReadAs<string>(),
                        Url = m.Value["url"].ReadAs<string>(),
                        Sizes = m.Value["sizes"].Select<KeyValuePair<string, JsonValue>, IMediaSize>(s => new MediaSize()
                        {
                            Type = s.Key,
                            Width = s.Value["w"].ReadAs<int>(),
                            Height = s.Value["h"].ReadAs<int>(),
                            Resize = s.Value["resize"].ReadAs<string>()
                        }).ToArray()
                    }).ToArray();

                if (tweet.ContainsKey("coordinates") && tweet["coordinates"] != null && tweet["coordinates"].ContainsKey("coordinates"))
                {
                    JsonArray coords = (JsonArray)tweet["coordinates"]["coordinates"];
                    message.Longitude = coords[0].ReadAs<double>();
                    message.Latitude = coords[0].ReadAs<double>();
                }
            }
            catch (Exception ex)
            {
                message = new Tweet();
                message.Text = "Exception: " + ex.Message;
            }

            return message;
        }


        private static string ParseTweetEntitiesToHtml(string message, JsonObject entities)
        {
            //resolve media urls
            if (entities.ContainsKey("media"))
                foreach (var ent in entities["media"])
                    message = StringReplace(
                        message,
                        ent.Value["url"].ReadAs<string>(),
                        string.Format(
                            "<a href=\"{0}\">{1}</a>",
                            ent.Value["url"].ReadAs<string>(),
                            ent.Value["display_url"].ReadAs<string>()
                        )
                    );

            //resolve urls
            if (entities.ContainsKey("urls"))
                foreach (var ent in entities["urls"])
                    message = StringReplace(
                        message,
                        ent.Value["url"].ReadAs<string>(),
                        string.Format(
                            "<a href=\"{0}\">{1}</a>",
                            ent.Value["url"].ReadAs<string>(),
                            string.IsNullOrEmpty(ent.Value["display_url"].ReadAs<string>()) ? ent.Value["url"].ReadAs<string>() : ent.Value["display_url"].ReadAs<string>()
                        )
                    );

            //resolve user mentions
            if (entities.ContainsKey("user_mentions"))
                foreach (var ent in entities["user_mentions"])
                    message = StringReplace(
                        message,
                        "@" + ent.Value["screen_name"].ReadAs<string>(),
                        string.Format(
                            "<a href=\"https://twitter.com/{0}\">@{0}</a>",
                            ent.Value["screen_name"].ReadAs<string>()
                        )
                    );

            //resolve hashtags
            if (entities.ContainsKey("hashtags"))
                foreach (var ent in entities["hashtags"])
                    message = StringReplace(
                        message,
                        "#" + ent.Value["text"].ReadAs<string>(),
                        string.Format(
                            "<a href=\"https://twitter.com/search?q=%23{0}\">#{0}</a>",
                            ent.Value["text"].ReadAs<string>()
                        )
                    );

            return message;
        }

        private static string StringReplace(string original, string pattern, string replacement)
        {
            int origStart = original.ToLowerInvariant().IndexOf(pattern.ToLowerInvariant());

            //safety
            if (origStart < 0)
                return original;

            string leftPart = original.Substring(0, origStart);
            string rightPart = original.Substring(origStart + pattern.Length);
            return string.Concat(leftPart, replacement, rightPart);
        }

        internal static bool IsValidTweetJson(JsonObject tweet)
        {
            if (tweet == null)
                return false;
            if (string.IsNullOrEmpty(tweet["text"].ReadAs<string>()))
                return false;
            if (tweet["user"] == null)
                return false;
            if (string.IsNullOrEmpty(tweet["user"]["screen_name"].ReadAs<string>()))
                return false;
            if (string.IsNullOrEmpty(tweet["user"]["name"].ReadAs<string>()))
                return false;
            if (string.IsNullOrEmpty(tweet["user"]["profile_image_url"].ReadAs<string>()))
                return false;

            return true;
        }

        internal static string SpecialEncode(string original)
        {
            string[,] naughtyChars = new string[,]
            {
                { "%", "%25" },
                { "$" , "%24" },
                { "&", "%26" },
                { "+", "%2B" },
                { ",", "%2C" },
                { "/", "%2F" },
                { ":", "%3A" },
                { ";", "%3B" },
                { "=", "%3D" },
                { "?", "%3F" },
                { "@", "%40" },
                { " ", "%20" },
                { "\"" , "%22" },
                { "<", "%3C" },
                { ">", "%3E" },
                { "#", "%23" },
                { "{", "%7B" },
                { "}", "%7D" },
                { "|", "%7C" },
                { "\\", "%5C" },
                { "^", "%5E" },
                { "~", "%7E" },
                { "[", "%5B" },
                { "]", "%5D" },
                { "`", "%60" }
            };

            for (int i = 0; i < naughtyChars.GetUpperBound(0); i++)
                original = original.Replace(naughtyChars[i, 0], naughtyChars[i, 1]);
            return original;
        }
    }
}
